Micron Document
baubs git

Node / mirrors / respira / commits / 7baf03e

Commit 7baf03e4f71689298458dd31bbe62e5003c9c02f


Parents : 03ba6c7
Author : Jan-Henrik Bruhn <jan-henrik.bruhn@offis.de>
Date : 2025-12-27T11:46:45+01:00

refactor: Improve useEffect patterns in AppHeader with usePrevious hook

Replace manual ref tracking with usePrevious custom hook for cleaner,
more maintainable code. Simplifies error state change detection by
eliminating manual ref updates and reducing complexity.

Resolves #36

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

Changes

2 files changed, 57 insertions(+), 34 deletions(-)


Diff

diff --git a/src/components/AppHeader.tsx b/src/components/AppHeader.tsx
index 0ec0976..20d9de2 100644
--- a/src/components/AppHeader.tsx
+++ b/src/components/AppHeader.tsx
@@ -1,7 +1,8 @@
-import { useState, useEffect, useRef } from "react";
+import { useState, useEffect } from "react";
import { useShallow } from "zustand/react/shallow";
import { useMachineStore } from "../stores/useMachineStore";
import { useUIStore } from "../stores/useUIStore";
+import { usePrevious } from "../hooks/usePrevious";
import { WorkflowStepper } from "./WorkflowStepper";
import { ErrorPopoverContent } from "./ErrorPopover";
import {
@@ -65,9 +66,11 @@ export function AppHeader() {
const [dismissedErrorCode, setDismissedErrorCode] = useState<number | null>(
null,
);
- const prevMachineErrorRef = useRef<number | undefined>(undefined);
- const prevErrorMessageRef = useRef<string | null>(null);
- const prevPyodideErrorRef = useRef<string | null>(null);
+
+ // Track previous values for comparison
+ const prevMachineError = usePrevious(machineError);
+ const prevErrorMessage = usePrevious(machineErrorMessage);
+ const prevPyodideError = usePrevious(pyodideError);
// Get state visual info for header status badge
const stateVisual = getStateVisualInfo(machineStatus);
@@ -89,29 +92,21 @@ export function AppHeader() {
// Auto-open/close error popover based on error state changes
/* eslint-disable react-hooks/set-state-in-effect */
useEffect(() => {
- const currentError = machineError;
- const prevError = prevMachineErrorRef.current;
- const currentErrorMessage = machineErrorMessage;
- const prevErrorMessage = prevErrorMessageRef.current;
- const currentPyodideError = pyodideError;
- const prevPyodideError = prevPyodideErrorRef.current;
-
// Check if there's any error now
const hasAnyError =
- machineErrorMessage || pyodideError || hasError(currentError);
+ machineErrorMessage || pyodideError || hasError(machineError);
// Check if there was any error before
const hadAnyError =
- prevErrorMessage || prevPyodideError || hasError(prevError);
+ prevErrorMessage || prevPyodideError || hasError(prevMachineError);
// Auto-open popover when new error appears
const isNewMachineError =
- hasError(currentError) &&
- currentError !== prevError &&
- currentError !== dismissedErrorCode;
+ hasError(machineError) &&
+ machineError !== prevMachineError &&
+ machineError !== dismissedErrorCode;
const isNewErrorMessage =
- currentErrorMessage && currentErrorMessage !== prevErrorMessage;
- const isNewPyodideError =
- currentPyodideError && currentPyodideError !== prevPyodideError;
+ machineErrorMessage && machineErrorMessage !== prevErrorMessage;
+ const isNewPyodideError = pyodideError && pyodideError !== prevPyodideError;
if (isNewMachineError || isNewErrorMessage || isNewPyodideError) {
setErrorPopoverOpen(true);
@@ -122,27 +117,24 @@ export function AppHeader() {
setErrorPopoverOpen(false);
setDismissedErrorCode(null); // Reset dismissed tracking
}
-
- // Update refs for next comparison
- prevMachineErrorRef.current = currentError;
- prevErrorMessageRef.current = currentErrorMessage;
- prevPyodideErrorRef.current = currentPyodideError;
- }, [machineError, machineErrorMessage, pyodideError, dismissedErrorCode]);
+ }, [
+ machineError,
+ machineErrorMessage,
+ pyodideError,
+ dismissedErrorCode,
+ prevMachineError,
+ prevErrorMessage,
+ prevPyodideError,
+ ]);
/* eslint-enable react-hooks/set-state-in-effect */
// Handle manual popover dismiss
const handlePopoverOpenChange = (open: boolean) => {
setErrorPopoverOpen(open);
- // If user manually closes it, remember the current error state to prevent reopening
- if (!open) {
- // For machine errors, track the error code
- if (hasError(machineError)) {
- setDismissedErrorCode(machineError);
- }
- // Update refs so we don't reopen for the same error message/pyodide error
- prevErrorMessageRef.current = machineErrorMessage;
- prevPyodideErrorRef.current = pyodideError;
+ // If user manually closes it, remember the current error code to prevent reopening
+ if (!open && hasError(machineError)) {
+ setDismissedErrorCode(machineError);
}
};

diff --git a/src/hooks/usePrevious.ts b/src/hooks/usePrevious.ts
new file mode 100644
index 0000000..d959bb3
--- /dev/null
+++ b/src/hooks/usePrevious.ts
@@ -0,0 +1,31 @@
+/**
+ * usePrevious Hook
+ *
+ * Returns the previous value of a state or prop
+ * Useful for comparing current vs previous values in effects
+ *
+ * Note: This uses ref access during render, which is safe for this specific use case.
+ * The pattern is recommended by React for tracking previous values.
+ */
+
+import { useRef } from "react";
+
+/* eslint-disable react-hooks/refs */
+export function usePrevious<T>(value: T): T | undefined {
+ const ref = useRef<{ value: T; prev: T | undefined }>({
+ value,
+ prev: undefined,
+ });
+
+ const current = ref.current.value;
+
+ if (value !== current) {
+ ref.current = {
+ value,
+ prev: current,
+ };
+ }
+
+ return ref.current.prev;
+}
+/* eslint-enable react-hooks/refs */

Served by rngit 1.3.3 - Generated in 0.04s